import { toInt } from './number'
import { isPrimitive, isUndefined } from './type'

export const get = <TDefault = unknown>(
    value: any,
    path: string,
    defaultValue?: TDefault
): TDefault => {
    const segments = path.split(/[.[\]]/g)
    let current: any = value
    for (const key of segments) {
        if (current === null) return defaultValue as TDefault
        if (current === undefined) return defaultValue as TDefault
        const dequoted = key.replace(/['"]/g, '')
        if (dequoted.trim() === '') continue
        current = current[dequoted]
    }
    if (current === undefined) return defaultValue as TDefault
    return current
}

export const clone = <T>(obj: T): T => {
    if (isPrimitive(obj)) {
        return obj
    }
    if (typeof obj === 'function') {
        return obj.bind({})
    }
    const newObj = new ((obj as object).constructor as { new (): T })()
    Object.getOwnPropertyNames(obj).forEach((prop) => {
        ;(newObj as any)[prop] = (obj as any)[prop]
    })
    return newObj
}
export const set = <T extends object, K>(initial: T, path: string, value: K): T => {
    if (!initial) return {} as T
    if (!path || value === undefined) return initial
    const segments = path.split(/[.[\]]/g).filter((x) => !!x.trim())
    const _set = (node: any) => {
        if (segments.length > 1) {
            const key = segments.shift() as string
            const nextIsNum = toInt(segments[0], null) !== null
            if (isUndefined(node[key])) {
                node[key] = nextIsNum ? [] : {}
            }
            _set(node[key])
        } else {
            node[segments[0]] = value
        }
    }
    const cloned = clone(initial)
    _set(cloned)
    return cloned
}

export const pick = <T extends object, TKeys extends keyof T>(
    obj: T,
    keys: TKeys[]
): Pick<T, TKeys> => {
    if (!obj) return {} as Pick<T, TKeys>
    return keys.reduce(
        (acc, key) => {
            if (Object.prototype.hasOwnProperty.call(obj, key)) acc[key] = obj[key]
            return acc
        },
        {} as Pick<T, TKeys>
    )
}

export const omit = <T, TKeys extends keyof T>(obj: T, keys: TKeys[]): Omit<T, TKeys> => {
    if (!obj) return {} as Omit<T, TKeys>
    if (!keys || keys.length === 0) return obj as Omit<T, TKeys>
    return keys.reduce(
        (acc, key) => {
            delete acc[key]
            return acc
        },
        { ...obj }
    )
}
const isObject = (value: any): value is object => {
    return !!value && value.constructor === Object
}
export const assign = <X extends Record<string | symbol | number, any>>(
    initial: X,
    override: X
): X => {
    if (!initial || !override) return initial ?? override ?? {}

    return Object.entries({ ...initial, ...override }).reduce((acc, [key, value]) => {
        return {
            ...acc,
            [key]: (() => {
                if (isObject(initial[key])) return assign(initial[key], value)
                return value
            })()
        }
    }, {} as X)
}
